
# include "dfxRay.h"

namespace DFX
{
    void Ray::DeTransform(const Matrix4 &m)
    {
        Matrix4 invMat;
        Matrix4 mat = m;

        mat.M2[0][3] = 0.0f;
        mat.M2[1][3] = 0.0f;
        mat.M2[2][3] = 0.0f;

        invMat = mat.GetInverse();

        Origin = mat.TransformVector3(Origin);
        Dir = mat.TransformVector3(Dir);
    }

    bool Ray::Intersects(const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, bool cull, Float32 *t)
    {
        Vector3 edge1 = v1 - v0;
        Vector3 edge2 = v2 - v0;

        Vector3 pvec = Dir.Cross(edge2);
        Float32 det = edge1.Dot(pvec);

        if (cull && det < 0.0001f)
            return false;
        else if (det < 0.0001f && det > -0.0001f)
            return false;

        Vector3 tvec = Origin - v0;
        Float32 u = tvec.Dot(pvec);

        if (u < 0.0f || u > det)
            return false;

        Vector3 qvec = tvec.Cross(edge1);
        Float32 v = Dir.Dot(qvec);

        if (v < 0.0f || u + v > det)
            return false;

        if (t)
        {
            Float32 invDet = 1.0f / det;
            
            *t = edge2.Dot(qvec);
            *t *= invDet;
        }

        return true;
    }

    bool Ray::Intersects(const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, bool cull, Float32 len, Float32 *t)
    {
        Vector3 edge1 = v1 - v0;
        Vector3 edge2 = v2 - v0;

        Vector3 pvec = Dir.Cross(edge2);
        Float32 det = edge1.Dot(pvec);

        if (cull && det < 0.0001f)
            return false;
        else if (det < 0.0001f && det > -0.0001f)
            return false;

        Vector3 tvec = Origin - v0;
        Float32 u = tvec.Dot(pvec);

        if (u < 0.0f || u > det)
            return false;

        Vector3 qvec = tvec.Cross(edge1);
        Float32 v = Dir.Dot(qvec);

        if (v < 0.0f || u + v > det)
            return false;

        if (t)
        {
            Float32 invDet = 1.0f / det;

            *t = edge2.Dot(qvec);
            *t *= invDet;

            if (*t > len)
                return false;
        }
        else
        {
            Float32 f = edge2.Dot(qvec) * (1.0f / det);
         
            if (f > len)
                return false;
        }

        return true;
    }

    bool Ray::Intersects(const Aabb &aabb, Float32 *t)
    {
        Float32 t0, t1, tmp;
        Float32 near = -999999.9f;
        Float32 far = 999999.9f;
        Float32 epsilon = 0.00001f;
        Vector3 max;

        if (fabs(Dir.X) < epsilon)
        {
            if (Origin.X < aabb.Min.X || Origin.X > aabb.Max.X)
                return false;
        }

        t0 = (aabb.Min.X - Origin.X) / Dir.X;
        t1 = (aabb.Max.X - Origin.X) / Dir.X;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (fabs(Dir.Y) < epsilon)
        {
            if (Origin.Y < aabb.Min.Y || Origin.Y > aabb.Max.Y)
                return false;
        }

        t0 = (aabb.Min.Y - Origin.Y) / Dir.Y;
        t1 = (aabb.Max.Y - Origin.Y) / Dir.Y;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (fabs(Dir.Z) < epsilon)
        {
            if (Origin.Z < aabb.Min.Z || Origin.Z > aabb.Max.Z)
                return false;
        }

        t0 = (aabb.Min.Z - Origin.Z) / Dir.Z;
        t1 = (aabb.Max.Z - Origin.Z) / Dir.Z;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (near > 0)
        {
            if (t)
                *t = near;
        }
        else
        {
            if (t)
                *t = far;
        }

        return true;
    }

    bool Ray::Intersects(const Aabb &aabb, Float32 len, Float32 *t)
    {
        Float32 t0, t1, tmp, final;
        Float32 near = -999999.9f;
        Float32 far = 999999.9f;
        Float32 epsilon = 0.00001f;
        Vector3 max;

        if (fabs(Dir.X) < epsilon)
        {
            if (Origin.X < aabb.Min.X || Origin.X > aabb.Max.X)
                return false;
        }

        t0 = (aabb.Min.X - Origin.X) / Dir.X;
        t1 = (aabb.Max.X - Origin.X) / Dir.X;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (fabs(Dir.Y) < epsilon)
        {
            if (Origin.Y < aabb.Min.Y || Origin.Y > aabb.Max.Y)
                return false;
        }

        t0 = (aabb.Min.Y - Origin.Y) / Dir.Y;
        t1 = (aabb.Max.Y - Origin.Y) / Dir.Y;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (fabs(Dir.Z) < epsilon)
        {
            if (Origin.Z < aabb.Min.Z || Origin.Z > aabb.Max.Z)
                return false;
        }

        t0 = (aabb.Min.Z - Origin.Z) / Dir.Z;
        t1 = (aabb.Max.Z - Origin.Z) / Dir.Z;

        if (t0 > t1)
        {
            tmp = t0;
            t0 = t1;
            t1 = tmp;
        }

        if (t0 > near)
            near = t0;
        if (t1 < far)
            far = t1;

        if (near > far)
            return false;
        if (far < 0)
            return false;

        if (near > 0)
            final = near;
        else
            final = far;

        if (final > len)
            return false;

        if (t)
            *t = final;

        return true;
    }
}